package com.paradogs.framework.database;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author: yumi
 * @date: 2024/10/8  23:27
 * @Description: 数据库持久化（异步管理）线程
 */
@Slf4j
@Component
public class ParadogsDBAsyncThread extends Thread implements ApplicationRunner {

    /** ======== Manager ======== */
    // 持久化线程注册表 [hash]databaseThread
    private static final Map<Integer, ParadogsDBAsyncThread> databaseThreadMap = new LinkedHashMap<>();

    // db 线程数量
    public static Integer size;
    @Value("${server.db-size:0}")
    public void setSize(Integer size) {
        ParadogsDBAsyncThread.size = size;
    }

    // 单次处理重试次数
    public static Integer retry;
    @Value("${server.db-retry:3}")
    public void setRetry(Integer retry) {
        ParadogsDBAsyncThread.retry = retry;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        for (int i = 0; i < size; i++) {
            ParadogsDBAsyncThread databaseThread = new ParadogsDBAsyncThread();
            databaseThread.setUuid(i);
            databaseThread.start();
            databaseThreadMap.put(i, databaseThread);
        }
    }

    /** 关闭持久化线程 */
    public static void shutdown() {
        for (ParadogsDBAsyncThread thread : databaseThreadMap.values()) {
            thread.RUNNING_FLAG = false;
            thread.dbQueue.add(() -> {});
        }
    }

    /** 线程是否均完全停止 */
    public static boolean isAlreadyShutdown() {
        for (ParadogsDBAsyncThread thread : databaseThreadMap.values()) {
            if (thread.RUNNING) { return false; }
        }
        return true;
    }

    /** 加入一个持久化任务
//     * 简单采用取余哈希，有需要可自行修改
     * 保证同个玩家的持久化处理按顺序进行
     */
    public static <T> CompletableFuture<T> join(Long id, Runnable exec) {
        if (exec == null) { return null; }
        ParadogsDBAsyncThread databaseThread = databaseThreadMap.get((size - 1) & hash(id)); // 注意这里还要 (size -1) & 转成下标
        CompletableFuture<T> cb = new CompletableFuture<>();
        databaseThread.dbQueue.add(() -> {
            // 代理一个异步回调操作
            exec.run();
            cb.complete(null);
        });
        return cb;
    }
    public static <T> CompletableFuture<T> join(Long id, Supplier<T> exec) {
        if (exec == null) { return null; }
        ParadogsDBAsyncThread databaseThread = databaseThreadMap.get((size - 1) & hash(id)); // 注意这里还要 (size -1) & 转成下标
        CompletableFuture<T> cb = new CompletableFuture<>();
        databaseThread.dbQueue.add(() -> {
            // 代理一个异步回调操作
            T o = exec.get();
            cb.complete(o);
        });
        return cb;
    }
    public static <T> CompletableFuture<T> join(Runnable exec) {
        return join(0L, exec);
    }

    /** 计算队列 key 哈希值：这里采用了 HashMap 的 hash() 方法 */
    private static int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    /** ======== Instance ======== */
    @Setter
    private Integer uuid;
    private boolean RUNNING_FLAG = true;
    private boolean RUNNING = true;
    private LinkedBlockingQueue<Runnable> dbQueue = new LinkedBlockingQueue<>();
    @Override
    public void run() {
        log.info("Database thread({}) start ...", uuid);
        while (RUNNING_FLAG || dbQueue.size() > 0) {
            for (int i = 0; i <= retry; i++) {
                try {
                    Runnable exec = dbQueue.take();
                    exec.run();
                    break;
                } catch (Exception e) {
                    log.error("!!! Database thread handle fail, retry({}), {}", i);
                    e.printStackTrace();
                }
            }
        }
        log.info("Database thread({}) stop ...", uuid);
        RUNNING = false;
    }

}
